#   Copyright 2013 Stacy Doss stacy.doss@gmail.com
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

package DIME::View::Browser;

# L@@K Broswer should maintain Column objects, Column objects should point to data and gui items
# Currently this is done via two data members in Browser (data and listbox)
# Care should be taken to decouple this, think long and hard before doing.

use lib 'DIME/lib';
use DIME::Utility;
use DIME::View::Filter;

sub new {
	my $class  = shift;
	my $parent = shift;
		
	my $O = {
        parent  => $parent,
        data    => [],
        listbox => [],
        active  => 0,
	};
		
	bless $O, $class;

    $O->buildFrame();

	return $O;
}



sub buildFrame {
    my $self = shift;

	$self->{frame} = $self->{parent}{MW}->Frame();

	$self->{xscroll} = $self->{frame}->Scrollbar(-orient => 'horizontal', -jump   => 1);
    $self->{xscroll}->pack(-side => 'bottom',
						   -anchor => 'se',
						   -fill => 'x',
						   -expand => 0);

	$self->{xscroll}->set(0, 1);
	$self->{xscroll}->configure(-command => [sub {$self->scroll_browser(@_)}],);

    $self->addColumn();
}



sub hide {
    my $self = shift;
    $self->{frame}->packForget();
}



sub show {
    my $self = shift;
	$self->{frame}->pack(-anchor => 'w', -side => 'top', -fill => 'y', -expand => 1);
}



sub refresh {
    my $self   = shift;
    my $column = shift;    

    my $listbox   = $self->{listbox}[$column];
    my $parentRef = $self->{data}[$column];

    if (arrayref($parentRef)) {
        
    }
    
    if (hashref($parentRef)) {
        
    }
}



sub addColumn {
    my $self = shift;
    my $data = shift;

    my $ndx = scalar @{$self->{listbox}};

	my $frame = $self->{frame}->Frame(-bg => 'grey');
	$frame->pack(-anchor => 'w', -side => 'left', -fill => 'y', -expand => 'both');

	# Make data listbox
	$listbox = $frame->Scrolled("Listbox", -scrollbars => "osoe");
	$listbox->pack(-anchor => 'w', -side => 'bottom', -fill   => 'y', -expand => 'both');
	
	$listbox->Subwidget('listbox')->configure(-fg               => 'black',
					           				  -bg               => 'white',
								        	  -width            => 25,
										      -exportselection  => 0,
	           								  -selectbackground => 'grey',
				        					  -selectmode       => 'extended');
	# Bind mouse buttons to listbox
	# Left <1> mouse button selects item
	# Right <3> mouse button pops up context menu
    $listbox->bind("<1>", [sub {$self->select($ndx)}]);
	$listbox->bind("<3>", [sub {$self->popup($ndx)}]);

	my @filters;
	foreach my $tuple (['key', 'bisque'], ['value', 'burlywood']) {
        my ($name, $bg) = @{$tuple};
        my $filter = new DIME::View::Filter($frame, $name, $bg)	

	# L@@K Make two filter boxes
#		$e->bind("<KeyPress-Return>", [sub {$self->refresh_list($c)}]);
#		
#		$self->{tk}{filter}{$k}{$c} ||= '.';
#		push @filters, $e;
	}

	# L@@K Take care of data stuff
# 	push @{$self->{tk}{col}}, {
#		w  => $w,
#		kf => $filters[0],
#		vf => $filters[1],
#	};

	# L@@K Add path and set reference
#	if (defined($k)) {
#		if (hashref($r)) {
#			push @{$self->{path}{a}}, "{$k}";
#			$self->{tk}{col}[-1]{r} = $r->{$k};
#		}
#		
#		if (arrayref($r)) {
#			# Remove [ and ] from key since it is really an element index
#			$k =~ s/\[|\]//g;
#			push @{$self->{path}{a}}, "[$k]" ;
#			$self->{tk}{col}[-1]{r} = $r->[$k];
#		}
#	} else {
#		@{$self->{path}{a}} = ('$self->');
#		$self->{tk}{col}[-1]{r} = $r;
#	}

#	$self->{value} = "";

	# L@@K Fill list for the first time
#	$self->refresh_list($c);

	# L@@K Take care of scrolling
#	if (@{$self->{tk}{col}} > 5) {
#		$self->{tk}{xscroll}->set(0, 4.0 / @{$self->{tk}{col}});
#		$self->scroll_browser('scroll', 1, 'units');
#	}
    
#	$self->set_scrollbar;
        
    push(@{$self->{listbox}}, $listbox->Subwidget('listbox'));
}



sub getType {
    my $value = shift;
    return (split(' ', $value))[0];
}



sub getValue {
    my $value = shift;
    return (split(' ', $value))[1];
}



sub select {
	my $self = shift;
	my $ndx  = shift; # Depth of target column (0 based)

    my $listbox = $self->{listbox}[$ndx];
    
	# Reset active column
	$self->{active} = $ndx;

	# Get current selection, return if no selection (This should never happen)
	my $cursel = ($listbox->curselection)[0];
	return unless defined $cursel;

    $self->removeColumns($ndx);
    
    # L@@K need to implement
#    # Reference to data structure that list is built from
#	my $w = $self->{tk}{col}[$ndx]{w};
#	my $r = $self->{tk}{col}[$ndx]{r}; 
#
    my $entry = $listbox->get($cursel);
	my $type = getType($entry);
	my $value = getValue($entry);
    my $pathType = '';

#	$w->activate($cursel);

    # L@@K Needs rework
	# If the current selection is a data containier
    if ($type =~ /^[@%]$/) {
        $self->addColumn();
        if (hashref($self->{data}[$ndx])) {
            $self->populate($self->{data}[$ndx]{$value});
            $pathType = '%'
        }

        if (arrayref($self->{data}[$ndx])) {
    		$value =~ s/[\[\]]//g;
            $self->populate($self->{data}[$ndx][$value]);
            $pathType = '@'
        }
	} else {
		# Must be a value ...
		$self->{value} = '';

		if (hashref($self->{data}[$ndx])) {
			$self->{value} = $self->{data}[$ndx]{$value};
            $pathType = '%'
		}

		if (arrayref($self->{data}[$ndx])) {
			$value =~ s/\[|\]//g;
			$self->{value} = $self->{data}[$ndx][$value] ;
            $pathType = '@'
		}
	}

    while ($self->{parent}->getPath()->size() > $ndx) {
        $self->{parent}->getPath()->delete();
    }

    $self->{parent}->getPath()->add($pathType, $value);
}



sub getChildRef {
	my $self = shift;
	my $parentRef = shift;
	my $name = shift;
    
    if (arrayref($parentRef)) {
        $name =~ s/[\[\]]//g;
        return $parentRef->[$name];            
    }
    
    if (hashref($parentRef)) {
        return $parentRef->{$name};
    }
    
    print STDERR "Ouch!\n";
}

sub getEditValue {
    my $self = shift;
    my $field = shift || 'value';
    
    return $self->{parent}->getEdit()->getValue($field);
}


sub add {
    my $self = shift;
    my $column = shift;
    my $type = shift;
    
	my $listbox = $self->{listbox}[$column];
	my $parentRef = $self->{data}[$column]; # Reference to data structure that list is built from

    my @selected = $listbox->curselection;
    for my $s (@selected) {
        my $ref   = $self->getChildRef($parentRef, getValue($listbox->get($s)));

        my $value;
        if ($type eq '@') { $value = []; }
        if ($type eq '%') { $value = {}; }
        if ($type eq '$') { $value = $self->getEditValue('value'); }
        
        if (arrayref($ref)) { push @{$ref}, $value; }
        if (hashref($ref))  { $ref->{$self->getEditValue('key')} = $value; }
    }
}



sub deleteSelectedItems {
    my $self = shift;
    my $column = shift;

	my $listbox = $self->{listbox}[$column];
	my $parentRef = $self->{data}[$column]; # Reference to data structure that list is built from

    my @selected = $listbox->curselection;
    for my $selection (reverse @selected) {
        my $name = getValue($listbox->get($selection));
        if (arrayref($parentRef)) {
            $name =~ s/[\[\]]//g;
        }
        $self->deleteItem($column, $name);
    }
    
    $self->refresh($column);
}



sub defineItem {
    my $self = shift;
    my $column = shift;
    my $type = shift;    

    my $parentRef = $self->{data}[$column];

    my $listbox = $self->{listbox}[$column];
    my @selected = $listbox->curselection;

	my $value = $self->getEditValue('value');

    if ($type eq '@') {
        $value = [];
    }

    if ($type eq '%') {
        $value = {};
    }
    
	foreach my $selection (@selected)  {
        my $name = getValue($listbox->get($selection));
        if (arrayref($parentRef)) {
            $name =~ s/[\[\]]//g;
            $parentRef->[$name] = $value;
        }

        if (hashref($parentRef)) {
            $parentRef->{$name} = $value;
        }
        
        # L@@K need to refresh listbox here        
	}
}



sub undefSelectedItems {
    my $self = shift;
    my $column = shift;

    my $parentRef = $self->{data}[$column];

    my $listbox = $self->{listbox}[$column];
    my @selected = $listbox->curselection;

	foreach my $selection (@selected)  {
        my $name = getValue($listbox->get($selection));
        if (arrayref($parentRef)) {
            $name =~ s/[\[\]]//g;
            $parentRef->[$name] = undef;
        }

        if (hashref($parentRef)) {
            $parentRef->{$name} = undef;
        }
        
        # L@@K need to refresh listbox here        
	}
    
}


sub insert {
    my $self     = shift;
    my $column   = shift;
    my $location = shift;
    my $type     = shift;
        
    my $parentRef = $self->{data}[$column];
    unless (arrayref($parentRef)) {
        $self->{parent}{MW}->Error("You can only 'insert' to an arrayref");
        return;
    }

    my $listbox = $self->{listbox}[$column];
    my @selected = $listbox->curselection;

	my $value = $self->getEditValue('value');

	foreach my $selection (reverse @selected)  {

        my $ndx = getValue($listbox->get($selection));
        $ndx =~ s/[\[\]]//g;

        if ($location eq 'Before') {
        } elsif ($location eq 'After') {
            $ndx++;
        } elsif ($location eq 'At Index') {
            $ndx = $self->getEditValue('key');
            if ($ndx !~ /^\d+$/) {
                print STDERR "Invalid index given '$ndx'\n";
                next;
            }
        } else {
            print STDERR "Can not insert without location\n";
        }

        my $value = $self->getEditValue('value');
        if ($type eq '@') {
            $value = [];
        } elsif ($type eq '%') {
            $value = {};
        }

        # This splices in the new data.
        # L@@K need to implement better using OOP
        my $end = scalar @{$parentRef};        
        if ($ndx < $end) {
            splice(@{$parentRef}, $ndx, 0, $value);
        } else {
            $parentRef->[$ndx] = $value;
            $end = $ndx;
        }

        # L@@K some old code to consider
        #	(splice @{$r}, $k, 1, $r->[$k], $self->{value}) if ($it eq "\$");
        #	(splice @{$r}, $k, 1, $r->[$k], []) if ($it eq '@');
        #	(splice @{$r}, $k, 1, $r->[$k], {}) if ($it eq '%');


#        L@@K Need to refresh list
#        for my $childNdx (0..$end) {
#            my $name = join($SEP, @{$path}, $childNdx);
#            $self->addListItem($name);
#            $self->refreshItem($name);
#        }
	}
}



sub renameSelectedItems {
    my $self = shift;
    my $column = shift;
    
    my $parentRef = $self->{data}[$column];

    unless (hashref($parentRef)) {
        $self->{parent}{MW}->Error("You can only 'rename' a hasref");
        return;
    }

    my $listbox = $self->{listbox}[$column];
    my @selected = $listbox->curselection;

	my $key = $self->getEditValue('key');
	if (($key !~ /^[a-zA-Z]*[0-9]*$/) and (scalar @{$selected} > 1)) {
		$self->{parent}{MW}->Error("Bad Expression, \"$rn\" must match \"/^[a-zA-Z]*[0-9]*\$/\" for multiple items\nAborting rename\n");
		return;
	}

	foreach my $selection (@selected)  {
        my $oldKey = getValue($listbox->get($selection));
		$parentRef->{$key} = $parentRef->{$oldKey};
		delete $parentRef->{$oldKey};
	}

    # L@@K need to add refresh
    #	$self->refresh_list($c);
}



sub apply {
    my $self = shift;
    my $column = shift;
    
}




sub copyItem {
    my $self   = shift;
    my $column = shift;
    my $name   = shift;
};        



sub deleteItem {
    my $self   = shift;
    my $column = shift;
    my $name   = shift;

    my $parentRef = $self->{data}[$column];

    if (arrayref($parentRef)) {
        splice(@{$parentRef}, $name, 1);   
    }

    if (hashref($parentRef)) {
        delete $parentRef->{$name};   
    }
    
    # L@@K need to refresh here
};



sub cutSelectedItems {
    my $self = shift;
    my $column = shift;

    $self->{clipboard} = [];
    
	my $listbox = $self->{listbox}[$column];
	my $parentRef = $self->{data}[$column]; # Reference to data structure that list is built from

    my @selected = $self->{listbox}[$column]->curselection;

    for my $selection (reverse @selected) {
        my $name = getValue($listbox->get($selection));
        if (arrayref($parentRef)) {
            $name =~ s/[\[\]]//g;
        }
        $self->copyItem($column, $name);
        $self->deleteItem($column, $name);  
    }
}



sub copySelectedItems {
    my $self = shift;
    my $column = shift;

    $self->{clipboard} = [];
    
	my $listbox = $self->{listbox}[$column];
	my $parentRef = $self->{data}[$column]; # Reference to data structure that list is built from

    my @selected = $self->{listbox}[$column]->curselection;

    for my $selection (reverse @selected) {
        my $name = getValue($listbox->get($selection));
        if (arrayref($parentRef)) {
            $name =~ s/[\[\]]//g;
        }
        $self->copyItem($column, $name);
    }
}




sub paste {
    my $self = shift;
    my $column = shift;
    
# OLD	$menu->command(-label   => 'Paste', -command => [sub {
#		$self->action_paste;
}



#sub action_paste {
#	my $self = shift;
#
#	my $c = $self->{act}{col};
#	my $r   = $self->{tk}{col}[$c]{r};
#
#	if (hashref($r)) {
#		if (hashref($self->{clipboard})) {
#			foreach my $k (keys %{$self->{clipboard}}) {
#				$r->{$k} = ref $self->{clipboard}{$k} ? dclone $self->{clipboard}{$k} : $self->{clipboard}{$k};
#			}
#		}
#		
#		if (arrayref($self->{clipboard})) {
#			foreach my $k (@{$self->{clipboard}}) {
#				$r->{$k} = undef;
#			}
#		}
#	}
#
#	if (arrayref($r)) {
#		if (hashref($self->{clipboard})) {
#			foreach my $k (keys %{$self->{clipboard}}) {
#				push @{$r}, ref $self->{clipboard}{$k} ? dclone $self->{clipboard}{$k} : $self->{clipboard}{$k};
#			}
#			
#		}
#		
#		if (arrayref($self->{clipboard})) {
#			foreach my $k (@{$self->{clipboard}}) {
#				push @{$r}, ref $k ? dclone $k : $k;
#			}
#		}
#	}
#	$self->refresh_list($c);
#	$self->{tk}{col}[$c]{w}->selectionClear(0, 'end');
#}



sub popup {
	my $self = shift;
	my $column = shift; # Depth of target column (0 based)
    my $type = shift;

	my $listbox = $self->{listbox}[$column];
	my $parentRef = $self->{data}[$column]; # Reference to data structure that list is built from

    my @selected = $listbox->curselection;
    my $needsSelection = scalar(@selected) ? 'normal' : 'disabled';
	my %state = (add      => $needsSelection,
				 delete   => $needsSelection,
				 undef    => $needsSelection,
				 cut_copy => $needsSelection,
				 rename   => $needsSelection,
				 insert   => 'disabled',
				 paste    => $needsSelection,
				 define   => $needsSelection);


	# Determine enabled/disabled menus
	my $ccp = '';
	foreach my $s (@selected) {
	    my $value = getValue($listbox->get($s));
        my $ref = $self->getChildRef($parentRef, $value);
#        print "selected $s, v $value, r $ref, pR $parentRef\n";
		$ccp = $parentRef unless $ccp;

		$state{cut_copy} = 'disabled' unless ($ccp eq $parentRef);

		$state{insert} = 'normal' if arrayref($parentRef);
		$state{rename} = 'normal' if hashref($parentRef);
		$state{paste}  = $state{add} = 'normal' if ((hashref($ref)) or (arrayref($ref)));
		$state{define} = 'disabled' if (defined $ref);
		$state{undef}  = 'disabled' if (not defined $ref);
	}

    $state{rename} = 'disabled' unless $self->getEditValue('key');
	$state{paste}  = 'disabled' unless (defined $self->{clipboard});


	my $menu = $self->{parent}{MW}->Menu(-tearoff => 0);

	# Add menu can add scalars, arrays and hashes
	my $addm = $menu->Menu(-tearoff => 0);

	foreach my $type (['$', 'Scalar'], ['@', 'Array'], ['%', 'Hash']) {
		$addm->command(-label => $type->[1], -command => [sub {$self->add($column, $type->[0])}]);
	}

	$menu->add('cascade', -label => 'Add', -state => $state{add}, -menu => $addm);

	# Delete/Undef works the same for any type
	$menu->add('command', -label => 'Delete', -state => $state{delete}, -command => [sub {$self->deleteSelectedItems($column)}]);

	my $defm = $menu->Menu(-tearoff => 0);

	foreach my $type (['@', 'Array'], ['%', 'Hash']) {
		$defm->command(-label => $type->[1], -command => [sub {$self->defineItem($column, $type->[0])}]);
	}

	$menu->add('cascade', -label => 'Define', -state => $state{define}, -menu => $defm);

	$menu->add('command', -label => 'Undef',  -state => $state{undef}, -command => [sub {$self->undefSelectedItems($column)}]);

	$menu->separator();

	#################################################################
	# Insert/Rename

	# Insert is only valid if we are in an array, but we can insert any type; before, after or at an element
	my $insm = $menu->Menu(-tearoff => 0);
		
	foreach my $location ('Before', 'After', 'At Index') {
		my $sm = $menu->Menu(-tearoff => 0);
		foreach my $type (['$', 'Scalar'], ['@', 'Array'], ['%', 'Hash']) {
			$sm->command(-label => $type->[1], -command => [sub {$self->insert($column, $location, $type->[0])}]);
		}
		$insm->add('cascade', -label => "$location", -menu => $sm);
	}
	$menu->add('cascade', -label => 'Insert', -state => $state{insert}, -menu => $insm);

	# Rename only works if we are in a hash
	$menu->add('command', -label => 'Rename', -state => $state{rename}, -command => [sub {$self->renameSelectedItems($column)}]);
	$menu->add('command', -label => 'Apply', -command => [sub {$self->apply($column)}]);


	$menu->separator();

	#################################################################
	# Cut/Copy/Paste
	$menu->command(-label   => 'Cut',   -state => $state{cut_copy}, -command => [sub {$self->cutSelectedItems($column)}]);
	$menu->command(-label   => 'Copy',  -state => $state{cut_copy}, -command => [sub {$self->copySelectedItems($column)}]);
	$menu->command(-label   => 'Paste', -state => $state{paste},    -command => [sub {$self->paste($column)}]);

	# Instantiate popup
	$menu->Popup(-overanchor => 'nw', -popanchor  => 'nw', -popover    => 'cursor');
}



sub removeColumns {
    my $self = shift;
	my $target = shift || $self->{active};
	
	# While there are columns to the right
	while (scalar @{$self->{listbox}} > ($target + 1)) {
		# Remove the listbox, get its parent and destroy
        # listbox is a sub-widget, so we need to get it's parents parent
		my $listbox = pop @{$self->{listbox}};
		$listbox->parent->parent->destroy;
		
		# Clean up path, ref and filter widgets
        $self->{parent}->getPath()->delete();
	}
}





# Only works for array or hash refs
sub populate {
    my $self = shift;
    my $data = shift;
    my $col  = shift || scalar @{$self->{listbox}} - 1;

    return unless defined $data;
        
	my $hash  = hashref($data);

    my @vals = $hash ? sort keys %{$data} : @{$data};

    my $ndx = -1; # Only needed for arrays
    my @list;
    for my $k (@vals) {

        # Only needed for arrays
		$ndx++;

		my $name = $hash ? $k : "[$ndx]";
		my $val  = $hash ? $data->{$k} : $k;

		# L@@K add filtering on key
#		if ($hash && $self->{tk}{opts}{filters}) {
#			next unless $k =~ /$self->{tk}{filter}{key}{$c}/;
#		}
#		if ($array && $self->{tk}{opts}{filters}) {
#			next unless $ndx =~ /$self->{tk}{filter}{key}{$c}/;
#		}

		# L@@K Add -- Filtered on value?
#		if ($self->{tk}{opts}{filters}) {
#			if (defined $val) { 
#				next unless $val =~ /$self->{tk}{filter}{value}{$c}/;
#			} else {
#				next if ($self->{tk}{filter}{value}{$c} ne ".");
#			}
#		}

		# Make list entry
		my $pre = "\$";

		if (hashref($val)) {
			$pre = '%';
			$val = '';
		} elsif (arrayref($val)) {
			$pre  = '@';
			$val  = '';
		} else {
			$val = (defined $val) ? " = $val" : " = <undef>";
		}

		push @list, "$pre $name$val";
    }

	$self->{listbox}[$col]->delete(0, 'end');
	$self->{listbox}[$col]->insert(0, @list);
	
	$self->{data}[$col] = $data;
	
    # L@@K Need to do
#	# Reset selections
#    foreach my $s (@sel) {
#        $w->selectionSet($s);
#    }
#
#	# Update path
#	$self->{pathstr} = join ('', @{$self->{path}{a}});
	
}

1;

__END__